﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ImmediateDisplayOrientationChanger
{
    public partial class Form1 : Form
    {

        private System.Collections.Hashtable hDisplays; //Key is DISPLAY_DEVICE, Value is DEVMODE
        private System.Collections.Hashtable hDevices; //Key is DeviceName, Value is DISPLAY_DEVICE
        private Panel selectedDisplay;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            hDisplays = new System.Collections.Hashtable();
            hDevices = new System.Collections.Hashtable();
            updateLinkedDisplays();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            int panelId = 10000;
            switch (e.KeyCode)
            {
                case Keys.Escape:
                    Application.Exit();
                    break;
                case Keys.H:
                    MessageBox.Show("Press F9,F10,F11 or F12 to rotate selected display" + Environment.NewLine
                        + "select display by clicking to it or pressing F1,F2,F3 or F4" + Environment.NewLine
                        + "selected display is orange, others appear blue" + Environment.NewLine
                        + "you can align the bottom lines of the secondary displays to the primary display by pressing F8" + Environment.NewLine
                        + "press F5 to refresh the display-information" + Environment.NewLine
                        + Environment.NewLine + "tested with Windows 7 Professional x64. No warranty."
                        , Application.ProductName);
                    break;
                case Keys.F1:
                    panelId = 0;
                    break;
                case Keys.F2:
                    panelId = 1;
                    break;
                case Keys.F3:
                    panelId = 2;
                    break;
                case Keys.F4:
                    panelId = 3;
                    break;
                case Keys.F5:
                    updateLinkedDisplays();
                    break;
                case Keys.F8:
                    alignBottomLines();
                    break;
                case Keys.F9:
                    rotateDisplay(NativeMethods.DMDO_DEFAULT);
                    break;
                case Keys.F10:
                    rotateDisplay(NativeMethods.DMDO_90);
                    break;
                case Keys.F11:
                    rotateDisplay(NativeMethods.DMDO_180);
                    break;
                case Keys.F12:
                    rotateDisplay(NativeMethods.DMDO_270);
                    break;
                default:
                    return;
            }
            if (displayPanel.Controls.Count > panelId)
                selectDisplay(displayPanel.Controls[panelId], new EventArgs());
        }

        //get linked displays into hashtables
        private void updateLinkedDisplays()
        {
            hDisplays.Clear();
            hDevices.Clear();
            
            DISPLAY_DEVICE ddev = NativeMethods.CreateDisplayDevice();
            int displayAdapterIndex = -1;

            // get all DisplayAdapter
            while (NativeMethods.EnumDisplayDevices(null, (uint)++displayAdapterIndex, ref ddev, 0))
            {
                if ((ddev.StateFlags & DisplayDeviceStateFlags.AttachedToDesktop) == DisplayDeviceStateFlags.AttachedToDesktop
                        || (ddev.StateFlags & DisplayDeviceStateFlags.PrimaryDevice) == DisplayDeviceStateFlags.PrimaryDevice) //is display active?
                {
                    DEVMODE dm = NativeMethods.CreateDevmode();
                    NativeMethods.EnumDisplaySettings(ddev.DeviceName, NativeMethods.ENUM_CURRENT_SETTINGS, ref dm);

                    if (!hDisplays.Contains(ddev)) //Add all displays to hDisplays
                    {
                        hDisplays.Add(ddev, dm);
                        hDevices.Add(ddev.DeviceName, ddev);
                    }
                }
            }
            updateDisplayPanel();
        }

        //draw displays to panel
        private void updateDisplayPanel()
        {
            bool first = true;

            displayPanel.Controls.Clear();

            //get offset for x- and y-direction (secondary displays may have negativ positions)
            var minx = hDisplays.Keys.OfType<DISPLAY_DEVICE>()
                .Min(ddev => ((DEVMODE)hDisplays[ddev]).dmPositionX);
            int xOffset = Math.Abs((int)(minx/10));
            var miny = hDisplays.Keys.OfType<DISPLAY_DEVICE>()
                .Min(ddev => ((DEVMODE)hDisplays[ddev]).dmPositionY);
            int yOffset = Math.Abs((int)(miny/10));


            //generate new panel for every display
            foreach (DISPLAY_DEVICE ddev in hDisplays.Keys)
            {
                Panel display = new Panel();
                display.Name = ddev.DeviceName;
                display.BorderStyle = BorderStyle.FixedSingle;
                
                //select first display
                if (first)
                {
                    first = false;
                    selectDisplay(display, new EventArgs());
                }
                else
                    display.BackColor = Color.Blue;

                //set height and weight of panels according to display-resolution
                display.Width = (int)(((DEVMODE)hDisplays[ddev]).dmPelsWidth / 10);
                display.Height = (int)(((DEVMODE)hDisplays[ddev]).dmPelsHeight / 10);
                display.Location = new Point((int)(((DEVMODE)hDisplays[ddev]).dmPositionX / 10)+xOffset, (int)(((DEVMODE)hDisplays[ddev]).dmPositionY / 10)+yOffset);
                display.Click += selectDisplay;
                
                //add information-label to panel
                Label infoLabel = new Label();
                infoLabel.AutoSize = true;
                infoLabel.Text = ((DEVMODE)hDisplays[ddev]).dmPelsWidth + " x " + ((DEVMODE)hDisplays[ddev]).dmPelsHeight;
                switch (((DEVMODE)hDisplays[ddev]).dmDisplayOrientation)
                {
                    case NativeMethods.DMDO_270:
                        infoLabel.Text += " @ 270° " + Environment.NewLine;
                        break;
                    case NativeMethods.DMDO_180:
                        infoLabel.Text += " @ 180° " + Environment.NewLine;
                        break;
                    case NativeMethods.DMDO_90:
                        infoLabel.Text += " @ 90° " + Environment.NewLine;
                        break;
                    default:
                    case NativeMethods.DMDO_DEFAULT:
                        infoLabel.Text += " @ 0° " + Environment.NewLine;
                        break;
                }
                infoLabel.Text += ddev.DeviceName + Environment.NewLine;
                if ((ddev.StateFlags & DisplayDeviceStateFlags.PrimaryDevice) == DisplayDeviceStateFlags.PrimaryDevice)
                    infoLabel.Text += "Primary Device" + Environment.NewLine;
              /*  if ((ddev.StateFlags & DisplayDeviceStateFlags.AttachedToDesktop) == DisplayDeviceStateFlags.AttachedToDesktop)
                    infoLabel.Text += "AttachedToDesktop" + Environment.NewLine;*/
                infoLabel.Text += "(" + ((DEVMODE)hDisplays[ddev]).dmPositionX + "," + ((DEVMODE)hDisplays[ddev]).dmPositionY + ")" + Environment.NewLine;
                infoLabel.Text += "F" + (displayPanel.Controls.Count + 1).ToString() + " to select";
                display.Controls.Add(infoLabel);
                
                displayPanel.Controls.Add(display);
            }
        }

        //align bottom lines of secondary displays to bottom line of primary display
        private void alignBottomLines()
        {
            //get bottom line of primary display
            var botLine = hDisplays.Keys.OfType<DISPLAY_DEVICE>()
                .Where(ddev => (ddev.StateFlags & DisplayDeviceStateFlags.PrimaryDevice) == DisplayDeviceStateFlags.PrimaryDevice)
                .Select(ddev => new { ((DEVMODE)hDisplays[ddev]).dmPositionY, ((DEVMODE)hDisplays[ddev]).dmPelsHeight })
                .First();
            int yMax = botLine.dmPelsHeight + botLine.dmPositionY;

            //update Positon of other displays
            foreach (DISPLAY_DEVICE ddev in hDisplays.Keys)
            {
                if ((ddev.StateFlags & DisplayDeviceStateFlags.PrimaryDevice) == DisplayDeviceStateFlags.PrimaryDevice)
                    continue;
                DEVMODE dm = (DEVMODE)hDisplays[ddev];
                dm.dmPositionY = yMax - dm.dmPelsHeight;
                NativeMethods.ChangeDisplaySettingsEx(ddev.DeviceName,ref dm,IntPtr.Zero,0,IntPtr.Zero);
            }

            updateLinkedDisplays();
        }

        //rotate selected display
        private void rotateDisplay(int newOrientation)
        {
            // get devmode of selected display
            DEVMODE dm = (DEVMODE)hDisplays[hDevices[selectedDisplay.Name]];

            //do we need to switch horizontal and vertical resolution?
            if ((newOrientation - dm.dmDisplayOrientation) % 2 != 0) 
            {
                int temp = dm.dmPelsHeight;
                dm.dmPelsHeight = dm.dmPelsWidth;
                dm.dmPelsWidth = temp;
            }

            //set new Orientation to devmode
            dm.dmDisplayOrientation = newOrientation;

            //activate new devmode
            NativeMethods.ChangeDisplaySettingsEx(selectedDisplay.Name, ref dm, IntPtr.Zero, 0, IntPtr.Zero);
            updateLinkedDisplays();
        }

        //select display
        private void selectDisplay(object sender, EventArgs e)
        {
            ((Panel)sender).BackColor = Color.Orange;
            selectedDisplay = (Panel)sender;
            statusLabel.Text = selectedDisplay.Name + " is selected";
            foreach (Panel p in displayPanel.Controls)
            {
                if (p.Name != selectedDisplay.Name)
                    p.BackColor = Color.Blue;
            }
        }

    }
}
